﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GraphosData
{

	public class Graph
	{

		/// <summary>
		/// All nodes in the graph
		/// </summary>
		public IEnumerable<Node> Nodes
		{
			get { return m_personToNode.Values; }
		}

        /// <summary>
        /// Gets a list of Nodes based on relations between nodes
        /// </summary>
        /// <param name="node">The node the relation is from</param>
        /// <param name="relations">The enumeration of relations which should be considered</param>
        /// <returns></returns>
        public IEnumerable<Node> GetNodeRelations(Node node, IEnumerable<Relation> relations)
        {
            return from n in Nodes
                   where relations.Contains(node.RelationOf(n))
                   select n;
        }

        /// <summary>
        /// Gets all the possible relations associated with a particular node
        /// </summary>
        /// <param name="node">The node in question</param>
        /// <returns>An enumerable set of relations</returns>
        public IEnumerable<Relation> RelationsOf(Node node)
        {
            return (from n in node.AdjacentNodes
                    select (node.RelationOf(n))).Distinct();
        }
		/// <summary>
		/// Gets the node corresponding with person
		/// </summary>
		/// <param name="person">The person</param>
		/// <returns></returns>
		public Node GetNode(Person person)
		{
			Node node;
			m_personToNode.TryGetValue(person, out node);
			return node;
		}

		/// <summary>
		/// Add a node to the graph using the following Person data
		/// </summary>
		/// <param name="person"></param>
		/// <returns></returns>
		public Node AddNode(Person person)
		{
			Node node;
			if (!m_personToNode.TryGetValue(person, out node))
			{
				node = new Node(person);
				m_personToNode.Add(person, node);
			}
			return node;
		}

		/// <summary>
		/// Add an edge from p1 to p2 with the supplied Relation
		/// </summary>
		/// <param name="p1">The "from" person</param>
		/// <param name="p2">The "to" person</param>
		/// <param name="relation">The relationship between p1 and p2</param>
		public void AddEdge(Person p1, Person p2, Relation relation)
		{
			AddEdge(GetNode(p1), GetNode(p2), relation);
		}


		// add an edge using the nodes
		private void AddEdge(Node n1, Node n2, Relation relation)
		{
			if (n1 == null || n2 == null)
				return;

			n1.AddAdjacentNode(n2, relation);
			n2.AddAdjacentNode(n1, Converse(n1.Person.Gender, relation));
		}

        /// <summary>
        /// Get the backwards relationship givin the current gender and relationship
        /// </summary>
        /// <param name="gender">The gender of the given relationship</param>
        /// <param name="relation">The Relation</param>
        /// <returns>The backward Relation...(ie Uncle -> Niece or Nephew, Husband -> Wife, etc</returns>
		private Relation Converse(Gender gender, Relation relation)
		{
			switch (relation)
			{
				case Relation.Apostle:
					return Relation.ApostleOf;
                case Relation.ApostleOf:
                    return Relation.Apostle;
                case Relation.Disciple:
                    return Relation.DiscipleOf;
                case Relation.DiscipleOf:
                    return Relation.Disciple;
				case Relation.Brother:
					return gender == Gender.Male ? Relation.Brother : Relation.Sister;
				case Relation.Concubine:
					return Relation.ConcubineOf;
				case Relation.ConcubineOf:
					return Relation.Concubine;
				case Relation.Daughter:
					return gender == Gender.Male ? Relation.Father : Relation.Mother;
				case Relation.Family:
					return Relation.Family;
				case Relation.Father:
					return gender == Gender.Male ? Relation.Son : Relation.Daughter;
				case Relation.Grandson:
					return gender == Gender.Male ? Relation.GrandFather : Relation.GrandMother;
				case Relation.GrandDaughter:
					return gender == Gender.Male ? Relation.GrandFather : Relation.GrandMother;
				case Relation.GrandFather:
					return gender == Gender.Male ? Relation.Grandson : Relation.GrandDaughter;
				case Relation.GrandMother:
					return gender == Gender.Male ? Relation.Grandson : Relation.GrandDaughter;	
				case Relation.Husband:
					return Relation.Wife;
				case Relation.MaidServant:
                    return Relation.MaidServantOf;
                case Relation.MaidServantOf:
                    return Relation.MaidServant;
				case Relation.Mother:
					return gender == Gender.Male ? Relation.Son : Relation.Daughter;
				case Relation.Sister:
					return gender == Gender.Male ? Relation.Brother : Relation.Sister;
				case Relation.Son:
					return gender == Gender.Male ? Relation.Father : Relation.Mother;
				case Relation.Wife:
					return Relation.Husband;
				case Relation.Uncle:
					return gender == Gender.Male ? Relation.Nephew : Relation.Niece;
				case Relation.Aunt:
					return gender == Gender.Male ? Relation.Nephew : Relation.Niece;
				case Relation.Nephew:
					return gender == Gender.Male ? Relation.Uncle : Relation.Aunt;
				case Relation.Niece:
					return gender == Gender.Male ? Relation.Uncle : Relation.Aunt;
				default:
					return Relation.Unknown;
			}
		}
       
		// the sorted dictionary mapping persons to nodes
		SortedDictionary<Person, Node> m_personToNode = new SortedDictionary<Person, Node>();
	} 



	/// <summary>
	/// A Node
	/// </summary>
	public class Node : IComparable<Node>
	{

		/// <summary>
		/// Creates a new instance of the Node class
		/// </summary>
		/// <param name="person">The person data for this node</param>
		public Node(Person person)
		{
			m_person = person;
		}

		/// <summary>
		/// Gets the person
		/// </summary>
		public Person Person
		{
			get { return m_person; }
		}

		/// <summary>
		/// Gets all the adjacent nodes to the instance
		/// </summary>
		public IEnumerable<Node> AdjacentNodes
		{
			get { return m_nodeToRelation.Keys; }
		}

		/// <summary>
		/// Gets the relationship between the instance and the supplied node
		/// </summary>
		/// <param name="node">The node which the relationship is to</param>
		/// <returns>A Relation</returns>
		public Relation RelationOf(Node node)
		{
			Relation rel;
			if (!m_nodeToRelation.TryGetValue(node, out rel))
				return Relation.Unknown;
			return rel;
		}

		/// <summary>
		/// Adds an adjacent node to the instance
		/// </summary>
		/// <param name="node">The node to add</param>
		/// <param name="relation">The relationship of the instance with the node</param>
		public void AddAdjacentNode(Node node, Relation relation)
		{
			m_nodeToRelation[node] = relation;
		}

		// override for easy debugging
		public override string ToString()
		{
            return Person.FirstName + " " + Person.LastName;
		}

		private Person m_person;
		private Dictionary<Node, Relation> m_nodeToRelation = new Dictionary<Node, Relation>();


        #region IComparable<Node> Members

        public int CompareTo(Node other)
        {
            return RelationOf(other).CompareTo(other.RelationOf(this));
        }

        #endregion
    } 

}
